#ifndef __M_HTTP_H__
#define __M_HTTP_H__

#include <fstream>
#include <sys/stat.h>
#include <regex>
#include <unordered_map>
#include "Buffer.hpp"
#include "server.hpp"

namespace Muduo
{
    //工具类---工具类中的成员函数一般都写成静态成员函数
    class Util
    {
    public:
        //字符串分割函数---将src字符串按照分割字符串sep进行分割，得到的各个字符串放到array中，最终返回子串的数量
        static size_t  Split(const std::string& src, const std::string& sep, std::vector<std::string>* array)
        {
            int offset = 0;
            while(offset < src.size())
            {
                //在src字符串中偏移量offset处，开始向后查找sep字符/子串，返回查找到的位置
                size_t pos = src.find(sep, offset);
                if(pos == std::string::npos)
                {
                    //走到这里表示没有找到特定的字符---说明整个src字符串就是一整个子串
                    array->push_back(src.substr(offset));
                    return array->size();
                }
                //当前字符串是一个空的，没有内容
                if(pos == offset)
                {
                    offset = pos + 1;
                    continue;
                }
                //走到这里说明找到了sep字符，截取字符就可以了
                array->push_back(src.substr(offset, pos - offset));
                //更新寻找sep字符的起始位置
                offset = pos + 1;
            }
            return array->size();
        }
    public:
        //读取文件的所有内容，将读取的内容放到Buffer对象中
        static bool ReadFile(const std::string& filename, Muduo::Buffer* buf)
        {
            //以二进制的方式读取文件
            std::ifstream ifs(filename, std::ios::binary);
            if(ifs.is_open() == false)
            {
                LOGERROR("open %s file failed!", filename.c_str());
                return false;
            }
            //获取文件的大小
            size_t fsize = 0;
            ifs.seekg(0, ifs.end);
            fsize = ifs.tellg();
            ifs.seekg(0, ifs.beg);//移动到文件的起始位置，准备读取文件；
            //读取文件
            //保证buf有足够的空间写入
            buf->EnsureWriteSpace(fsize);
            ifs.read(buf->WritePosition(), fsize);
            if(ifs.good() == false)//判断读取文件是否成功
            {
                LOGERROR("read %s file failed!", filename.c_str());
                ifs.close();
                return false;
            }
            //移动buf的写位置偏移
            buf->MoveWriteOffest(fsize);
            ifs.close();
            return true;
        }
        //读取文件的所有内容，将读取的内容放到一个字符串中
        static bool ReadFile(const std::string &filename, std::string *buf) {
            std::ifstream ifs(filename, std::ios::binary);
            if (ifs.is_open() == false) 
            {
                LOGERROR("OPEN %s FILE FAILED!!", filename.c_str());
                return false;
            }
            size_t fsize = 0;
            ifs.seekg(0, ifs.end);//跳转读写位置到末尾
            fsize = ifs.tellg();  //获取当前读写位置相对于起始位置的偏移量，从末尾偏移刚好就是文件大小
            ifs.seekg(0, ifs.beg);//跳转到起始位置
            buf->resize(fsize); //开辟文件大小的空间
            ifs.read(&(*buf)[0], fsize);
            if (ifs.good() == false) 
            {
                LOGERROR("READ %s FILE FAILED!!", filename.c_str());
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }
        //向文件写入数据
        static bool WriteFile(const std::string& filename, Muduo::Buffer& buf)
        {
            //trunc是将文件的大小截断为0，即就是覆盖文件中原有的内容，从文件的起始位置写入；
            std::ofstream ofs(filename, std::ios::binary | std::ios::trunc);
            if(ofs.is_open() == false)
            {
                LOGERROR("open %s file failed!", filename.c_str());
                return false;
            }
            //写入数据
            ofs.write(buf.ReadPosition(), buf.ReadAbleSize());
            if(ofs.good() == false)
            {
                LOGERROR("write %s file failed!", filename.c_str());
                ofs.close();
                return false;
            }
            buf.MoveReadOffest(buf.ReadAbleSize());
            ofs.close();
            return true;
        }
        //向文件写入数据
        static bool WriteFile(const std::string &filename, const std::string &buf) 
        {
            std::ofstream ofs(filename, std::ios::binary | std::ios::trunc);
            if (ofs.is_open() == false) 
            {
                LOGERROR("OPEN %s FILE FAILED!!", filename.c_str());
                return false;
            }
            ofs.write(buf.c_str(), buf.size());
            if (ofs.good() == false) 
            {
                LOGERROR("WRITE %s FILE FAILED!", filename.c_str());
                ofs.close();    
                return false;
            }
            ofs.close();
            return true;
        }
        //URL编码，避免URL中资源路径与查询字符串中的特殊字符与HTTP请求中的特殊字符产生歧义
        //编码格式：将特殊字符的ASCII值，转换为两个16进制字符，前缀%； 比如 C++ ---> C%2B%2B
        //不编码的特殊字符：RFC3986文档规定字符 . - _ ~ 和 字母、数字属于绝对不编码字符
        //RFC3986文档规定，特殊字符编码格式：%HH
        //W3C标准中规定，查询字符串中的空格，需要编码为 + ， 解码则是 + 转为空格
        static std::string UrlEncode(const std::string url, bool convertSpaceTOPlus)
        {
            std::string ret;
            for(auto& c : url)
            {
                //如果是字符. - _ ~ 和 字母、数字不需要编码; isalnum函数是判断一个字符是否为字母或者数字字符；
                if(c == '.' || c == '-' || c == '_' || c == '~' || isalnum(c))
                {
                    ret += c;
                }
                else if(c == ' ' && convertSpaceTOPlus)
                {
                    ret += '+';
                }
                else
                {
                    char tmp[4] = {0};
                    snprintf(tmp, sizeof(tmp), "%%%02X", c);
                    ret += tmp;
                }
            }
            return ret;
        }
        static char HEXTOI(char c)
        {
            char res;
            if(c >= '0' && c <= '9')
            {
                res = c - '0';
            }
            else if (c >= 'a' && c <= 'z')
            {
                res = c - 'a' + 10;
            }
            else if(c >= 'A' && c <= 'Z')
            {
                res = c - 'A' + 10;
            }
            return res;
        }
        //URL解码
        static std::string UrlDecode(const std::string url, bool convertPlusTOSpace)
        {

            //遇到%字符，则将紧随其后的2个字符，转换为数字，第一个数字左移4位置(乘以16)，然后加上第二个数字；
            std::string ret;
            for(int i = 0; i < url.size(); ++i)
            { 
                if(url[i] == '+' && convertPlusTOSpace)
                {
                    ret += ' ';
                }
                else if(url[i] == '%' && (i+2) < url.size())
                {
                    char a = HEXTOI(url[i+1]);
                    char b = HEXTOI(url[i+2]);
                    char c = a * 16 + b;
                    ret += c;
                    i += 2;
                }
                else 
                {
                    ret += url[i];
                }
            }
            return ret;
        }
        //响应状态码的描述信息获取
        static std::string StatuDesc(int statCode)
        {
            //定义为一个静态的局部变量，这样就不用在每次进入这个函数的时候都构造这个变量了；
            static std::unordered_map<int, std::string> statMsg = {
                {100,  "Continue"},
                {101,  "Switching Protocol"},
                {102,  "Processing"},
                {103,  "Early Hints"},
                {200,  "OK"},
                {201,  "Created"},
                {202,  "Accepted"},
                {203,  "Non-Authoritative Information"},
                {204,  "No Content"},
                {205,  "Reset Content"},
                {206,  "Partial Content"},
                {207,  "Multi-Status"},
                {208,  "Already Reported"},
                {226,  "IM Used"},
                {300,  "Multiple Choice"},
                {301,  "Moved Permanently"},
                {302,  "Found"},
                {303,  "See Other"},
                {304,  "Not Modified"},
                {305,  "Use Proxy"},
                {306,  "unused"},
                {307,  "Temporary Redirect"},
                {308,  "Permanent Redirect"},
                {400,  "Bad Request"},
                {401,  "Unauthorized"},
                {402,  "Payment Required"},
                {403,  "Forbidden"},
                {404,  "Not Found"},
                {405,  "Method Not Allowed"},
                {406,  "Not Acceptable"},
                {407,  "Proxy Authentication Required"},
                {408,  "Request Timeout"},
                {409,  "Conflict"},
                {410,  "Gone"},
                {411,  "Length Required"},
                {412,  "Precondition Failed"},
                {413,  "Payload Too Large"},
                {414,  "URI Too Long"},
                {415,  "Unsupported Media Type"},
                {416,  "Range Not Satisfiable"},
                {417,  "Expectation Failed"},
                {418,  "I'm a teapot"},
                {421,  "Misdirected Request"},
                {422,  "Unprocessable Entity"},
                {423,  "Locked"},
                {424,  "Failed Dependency"},
                {425,  "Too Early"},
                {426,  "Upgrade Required"},
                {428,  "Precondition Required"},
                {429,  "Too Many Requests"},
                {431,  "Request Header Fields Too Large"},
                {451,  "Unavailable For Legal Reasons"},
                {501,  "Not Implemented"},
                {502,  "Bad Gateway"},
                {503,  "Service Unavailable"},
                {504,  "Gateway Timeout"},
                {505,  "HTTP Version Not Supported"},
                {506,  "Variant Also Negotiates"},
                {507,  "Insufficient Storage"},
                {508,  "Loop Detected"},
                {510,  "Not Extended"},
                {511,  "Network Authentication Required"}
            };
            auto it = statMsg.find(statCode);
            if(it != statMsg.end())
            {
                return it->second;
            }
            return "UnKnow";
        }
        //根据文件后缀名获取文件mime
        static std::string ExMime(const std::string& filename)
        {
            static std::unordered_map<std::string, std::string> mimeMsg = {
                {".aac",        "audio/aac"},
                {".abw",        "application/x-abiword"},
                {".arc",        "application/x-freearc"},
                {".avi",        "video/x-msvideo"},
                {".azw",        "application/vnd.amazon.ebook"},
                {".bin",        "application/octet-stream"},
                {".bmp",        "image/bmp"},
                {".bz",         "application/x-bzip"},
                {".bz2",        "application/x-bzip2"},
                {".csh",        "application/x-csh"},
                {".css",        "text/css"},
                {".csv",        "text/csv"},
                {".doc",        "application/msword"},
                {".docx",       "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
                {".eot",        "application/vnd.ms-fontobject"},
                {".epub",       "application/epub+zip"},
                {".gif",        "image/gif"},
                {".htm",        "text/html"},
                {".html",       "text/html"},
                {".ico",        "image/vnd.microsoft.icon"},
                {".ics",        "text/calendar"},
                {".jar",        "application/java-archive"},
                {".jpeg",       "image/jpeg"},
                {".jpg",        "image/jpeg"},
                {".js",         "text/javascript"},
                {".json",       "application/json"},
                {".jsonld",     "application/ld+json"},
                {".mid",        "audio/midi"},
                {".midi",       "audio/x-midi"},
                {".mjs",        "text/javascript"},
                {".mp3",        "audio/mpeg"},
                {".mpeg",       "video/mpeg"},
                {".mpkg",       "application/vnd.apple.installer+xml"},
                {".odp",        "application/vnd.oasis.opendocument.presentation"},
                {".ods",        "application/vnd.oasis.opendocument.spreadsheet"},
                {".odt",        "application/vnd.oasis.opendocument.text"},
                {".oga",        "audio/ogg"},
                {".ogv",        "video/ogg"},
                {".ogx",        "application/ogg"},
                {".otf",        "font/otf"},
                {".png",        "image/png"},
                {".pdf",        "application/pdf"},
                {".ppt",        "application/vnd.ms-powerpoint"},
                {".pptx",       "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
                {".rar",        "application/x-rar-compressed"},
                {".rtf",        "application/rtf"},
                {".sh",         "application/x-sh"},
                {".svg",        "image/svg+xml"},
                {".swf",        "application/x-shockwave-flash"},
                {".tar",        "application/x-tar"},
                {".tif",        "image/tiff"},
                {".tiff",       "image/tiff"},
                {".ttf",        "font/ttf"},
                {".txt",        "text/plain"},
                {".vsd",        "application/vnd.visio"},
                {".wav",        "audio/wav"},
                {".weba",       "audio/webm"},
                {".webm",       "video/webm"},
                {".webp",       "image/webp"},
                {".woff",       "font/woff"},
                {".woff2",      "font/woff2"},
                {".xhtml",      "application/xhtml+xml"},
                {".xls",        "application/vnd.ms-excel"},
                {".xlsx",       "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
                {".xml",        "application/xml"},
                {".xul",        "application/vnd.mozilla.xul+xml"},
                {".zip",        "application/zip"},
                {".3gp",        "video/3gpp"},
                {".3g2",        "video/3gpp2"},
                {".7z",         "application/x-7z-compressed"}
            };
            //获取文件后缀
            size_t pos = filename.find_last_of('.');
            if(pos == std::string::npos)
            {
                //没有找到文件后缀，就表示当前文件是一个二进制流
                return "application/octet-stream";
            }
            std::string extension = filename.substr(pos);
            //根据扩展名获取mime
            auto it = mimeMsg.find(extension);
            if(it != mimeMsg.end())
            {
                return it->second;
            }
            return "application/octet-stream";
        }
        //判断一个文件是一个目录
        static bool IsDirectory(const std::string& filename)
        {
            //获取文件的属性，stat接口是用来获取文件状态的
            struct stat st;
            int ret = stat(filename.c_str(), &st);
            if(ret < 0)
            {
                return false;
            }
            //宏S_ISDIR是通过文件的属性来判断，是否是一个目录
            return S_ISDIR(st.st_mode);
        }
        //判断一个文件是一个普通文件
        static bool IsRegular(const std::string& filename)
        {
            //获取文件的属性
            struct stat st;
            int ret = stat(filename.c_str(), &st);
            if(ret < 0)
            {
                return false;
            }
            //宏S_ISDIR是通过文件的属性来判断，是否是一个普通文件
            return S_ISREG(st.st_mode);
        }
        //HTTP请求的资源路径有效性的判断
        /* /index.html中前面的 / 叫做相对根目录，也叫做web根目录，其实就是映射的是服务器上的某个子目录，/index.html想要表达的意思就是，
        客户端只能请求相对根目录中的资源，其他地方的资源都不予理会，比如客户单想要访问 /../login 这个路径中的 .. 会让路径的查找跑到相对目录
        之外，这是不合理的，不安全的；*/
        static bool ValidPath(const std::string& path)
        {
            /*判断HTTP请求的资源路径有效性的思想：按照 / 进行路径的分割，根据有多少个子目录，计算目录的深度，有多少层，深度不能小于0；
            当遇到 .. 目录的深度就 -- */
            int level = 0;
            std::vector<std::string> subDir;
            Split(path, "/", &subDir);
            for(auto& dir : subDir)
            {
                if(dir == "..")
                {
                    level--;
                    //任意一层走出web根目录就任务资源路径有问题
                    if(level < 0)
                    {
                        return false;
                    }
                }
                else
                {
                    level++;
                }
            }
            return true;
        }
    };

    class HttpRequest
    {
    public:
        std::string _method;    //请求方法
        std::string _path;  //资源的路径
        std::string _version = "HTTP/1.1";   //协议版本
        std::string _body;  //请求正文
        std::smatch _matches;   //保存使用正则库regex解析字符串后提取的结果；
        std::unordered_map<std::string, std::string> _headers;  //保存请求头部的键值对
        std::unordered_map<std::string, std::string> _params;   //保存请求行中查询字符串和对应的参数
    public:
        //重置成员的内容防止出现处理下一个请求的时候上一个请求中的内容影响
        void ReSet()
        {
            _method.clear();
            _path.clear();
            _version = "HTTP/1.1";
            _body.clear();
            std::smatch match;
            _matches.swap(match);//用一个空的smath交换已有的smatch对象也可以达到清空_matches的效果；
            _headers.clear();
            _headers.clear();
        }
        //设置请求头部字段
        void SetHeader(const std::string& key, const std::string& val)
        {
            _headers[key] = val;
        }
        //查询某个头部字段是否在请求头部中存在
        bool HasHeader(const std::string& key)
        {
            auto it = _headers.find(key);
            if(it != _headers.end())
            {
                return true;
            }
            return false;
        }
        //根据key获取请求头部中对应的val
        std::string GetHeader(const std::string& key)
        {
            int res = HasHeader(key);
            if(res)
            {
                return _headers[key];
            }
            return "";
        }
        //设置查询字符串到请求行中
        void SetParam(const std::string& key, const std::string& val)
        {
            _params[key] = val;
        }
        //查询某个查询字符串是否在请求行中存在
        bool HasParam(const std::string& key)
        {
            auto it = _params.find(key);
            if(it != _params.end())
            {
                return true;
            }
            return false;
        }
        //根据key获取请求行对应的val
        std::string GetParam(const std::string& key)
        {
            bool res = HasParam(key);
            if(res)
            {
                return _params[key];
            }
            return "";
        }
        //获取正文的长度
        size_t ContentLength()
        {
            bool res = HasHeader("Content-Length");
            if(res)
            {
                std::string clen = GetHeader("Content-Length");
                return std::stol(clen);
            }
            return 0;
        }
        //判断是长连接还是短连接，短连接返回true，长连接返回false
        bool Close()
        {
            //没有Connection字段，或者Connection但是值是close，则都是短连接, 否则是长连接
            bool res = HasHeader("Connection");
            if(res && GetHeader("Connection") == "keep-alive")
            {
                return false;
            }
            return true;
        }
    };

    class HttpResponse
    {
    public:
        int _statCode;   //响应状态码
        std::string _version = "HTTP/1.1";   //协议版本        
        bool _redirectFlag; //重定向标志
        std::string _redirectUrl;   //重定向的资源路径        
        std::string _body;  //响应正文
        std::unordered_map<std::string, std::string> _headers;  //保存响应头部的键值对

    public:
        HttpResponse(int statCode = 200)
        :_statCode(statCode), _version("HTTP/1.1"), _redirectFlag(false)
        {}
        //重置响应信息
        void ReSet()
        {
            _statCode = 200;
            _version = "HTTP/1.1";
            _redirectFlag = false;
            _redirectUrl.clear();
            _body.clear();
            _headers.clear();
        }
        //设置响应头部字段
        void SetHeader(const std::string& key, const std::string& val)
        {
            _headers[key] = val;
        }
        //查询某个头部字段是否在响应头部中存在
        bool HasHeader(const std::string& key)
        {
            auto it = _headers.find(key);
            if(it != _headers.end())
            {
                return true;
            }
            return false;
        }
        //根据key获取响应头部中对应的val
        std::string GetHeader(const std::string& key)
        {
            int res = HasHeader(key);
            if(res)
            {
                return _headers[key];
            }
            return "";
        }
        //正文的设置
        void SetContent(const std::string& body, const std::string& type = "text/html")
        {
            _body = body;
            SetHeader("Content-Type", type);
        }
        //重定项的设置
        void SetRedirect(std::string& url, int status = 302)
        {
            _statCode = status;
            _redirectFlag = true;
            _redirectUrl = url;
        }
        //判断是长连接还是短连接，短连接返回true，长连接返回false
        bool Close()
        {
            //没有Connection字段，或者Connection但是值是close，则都是短连接, 否则是长连接
            bool res = HasHeader("Connection");
            if(res && GetHeader("Connection") == "keep-alive")
            {
                return false;
            }
            return true;
        }
    };

    typedef enum
    {
        RECV_HTTP_ERROR,    //接收请求出错了
        RECV_HTTP_LINE,     //接收请求行阶段
        RECV_HTTP_HEAD,     //接收请求头部阶段
        RECV_HTTP_BODY,     //接收请求正文阶段
        RECV_HTTP_OVER      //接收请求完毕阶段
    }HttpRecvStatus;
    #define MAX_LINE 8192
    class HttpContext
    {
    private:
        int _respStatus;    //响应状态码
        HttpRecvStatus _recvStatus;    //当前接收与解析的阶段状态
        HttpRequest _request;   //已经解析得到的请求信息放到 _request中
    private:
        //接收请求行
        bool RecvHttpLine(Muduo::Buffer* buf)
        {
            if(_recvStatus != RECV_HTTP_LINE)
            {
                return false;
            }
            //1. 获取一行数据, 带有末尾的换行的
            std::string line = buf->GetLineAndPop();
            //2. 需要考虑的一些要素：缓冲区中的数据不足一行，获取的一行数据超大
            if(line.size() == 0)
            {
                //缓冲区中的数据不足一行，则需要判断缓冲区的可读数据长度，如果很长了都不足一行，这是有问题的
                if(buf->ReadAbleSize() > MAX_LINE)
                {
                    _recvStatus = RECV_HTTP_ERROR;
                    _respStatus = 414;  //uri太长了---URI TOO LONG 
                    return false;
                }
                //缓冲区中数据不足一行，但是也不多，就等等新数据的到来
                return true;
            }
            if(line.size() > MAX_LINE)
            {
                _recvStatus = RECV_HTTP_ERROR;
                _respStatus = 414;  //uri太长了---URI TOO LONG 
                return false;
            }
            bool ret = ParseHttpLine(line);
            if(ret == false)
            {
                return false;
            }
            //请求行处理完毕，进入请求头部获取阶段
            _recvStatus = RECV_HTTP_HEAD;
            return true;
        }
        //解析请求行
        bool ParseHttpLine(std::string& line)
        {
            //末尾是换行字符则去掉 \r\n
            if(line.back() == '\n')
            {
                line.pop_back();
            }
            if(line.back() == '\r')
            {
                line.pop_back();
            }
            std::smatch matches;
            //std::regex::icase 表示在正则表达式匹配的时候忽略大小写；
            std::regex e("(GET|POST|HEAD|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\r\n|\n)?", std::regex::icase);
            //(GET|POST|HEAD|PUT|DELETE) 表示匹配并提取其中任意一个字符串；
            //([^?]*) 其中[^?]表示匹配非?字符， *表示匹配0次或者多次；([^?]*)表示提取非？字符0次或者多次，直到遇到？字符停止；
            //.*其中 . 表示匹配除了换行符外的任意一个字符， *表示匹配0次或者多次
            //由于？在正则表达式中有特殊含义，所以要表示字符串中的？需要进行转义，在字符串中要表示原始的？应该写 \\?
            //\\?(.*) 其中\\？表示原始的？字符，(.*)表示提取？之后的任意字符0次或者多次，直到遇到空格
            //(HTTP/1\\.[01]) 表示匹配以HTTP\1.开始的字符串，其中\\. 表示 . 字符是因为点在正则表达式中有特殊含义，需要进行转义；
            // (?:\r\n|\n)? 其中(?:...)表示匹配某个字符串，但是不提取；不规范请求行后面可能会出现没有\r\n,也没有\n的情况，使用？可以匹配0次或者一次；
            // 遇到不规范的请求行后也可以成功匹配并完成提取；
            // (?:\\?(.*))? 表示以？开始的字符串不要，需要提取？之后的字符串；最后的？表示匹配前面的模式0次或者1次；
            bool ret = std::regex_match(line, matches, e);
            if(!ret)
            {
                //解析请求行失败了，设置响应码和当前接收与解析的状态码
                _recvStatus = RECV_HTTP_ERROR;
                _respStatus = 400;  //BDA REQUEST
                return false;
            }
            //从请求行中使用正则库regex提取出来的信息，存储在matches中，要将matches中存储的信息放到 _request
            // GET /baidu/login?12324 HTTP/1.1
            // GET
            // /baidu/login
            // 12324
            // HTTP/1.1
            //请求方法的获取
            _request._method = matches[1];
            //为了防止有些请求方法是小写的get，post，put....将其转换为大写，方便后面请求方法的匹配
            std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);
            //请求行中的查询字符串中的某些字符可能是经过Url编码的，所以需要先进行Url解码，但是不需要将+字符转为空格
            _request._path = Util::UrlDecode(matches[2], false);
            //协议版本的获取
            _request._version = matches[4];
            //对查询字符串进行分割处理
            std::vector<std::string> queryStrarray;
            std::string queryStr = matches[3];
            /*查询字符串的格式：key=val&key=val.... 先以&字符分割多个查询字符串，再以 = 字符分割一个查询字符串的key和value，最后
            再进行URL解码，由于W3C标准中规定，查询字符串中的空格，需要编码为 + ， 解码则是 + 转为空格，所以要考虑将 + 字符的解码*/
            Util::Split(queryStr, "&", &queryStrarray);
            for(auto& str : queryStrarray)
            {
                auto pos = str.find("=");
                if(pos == std::string::npos)
                {
                    //解析请求行失败了，设置响应码和当前接收与解析的状态码
                    _recvStatus = RECV_HTTP_ERROR;
                    _respStatus = 400;  //BDA REQUEST
                    return false;
                }
                std::string key = Util::UrlDecode(str.substr(0, pos), true);
                std::string val = Util::UrlDecode(str.substr(pos+1), true);
                _request.SetParam(key, val);
            }
            return true;
        }
        //接收请求头部
        bool RecvHttpHead(Muduo::Buffer* buf)
        {
            if(_recvStatus != RECV_HTTP_HEAD)
            {
                return false;
            }
            //一行一行取出数据，知道遇到空行为止，头部格式 key: val\r\nkey: val\r\n...
            while(1)
            {
                //从GetLineAndPop中获取的数据是带有换行的
                std::string line = buf->GetLineAndPop();
                //2. 需要考虑的一些要素：缓冲区中的数据不足一行，获取的一行数据超大
                if(line.size() == 0)
                {
                    //缓冲区中的数据不足一行，则需要判断缓冲区的可读数据长度，如果很长了都不足一行，这是有问题的
                    if(buf->ReadAbleSize() > MAX_LINE)
                    {
                        _recvStatus = RECV_HTTP_ERROR;
                        _respStatus = 414;  //uri太长了---URI TOO LONG 
                        return false;
                    }
                    //缓冲区中数据不足一行，但是也不多，就等等新数据的到来
                    return true;
                }
                if(line.size() > MAX_LINE)
                {
                    _recvStatus = RECV_HTTP_ERROR;
                    _respStatus = 414;  //uri太长了---URI TOO LONG 
                    return false;
                }
                //如果遇到空行，则请求头部就读取完毕
                if(line == "\n" || line == "\r\n")
                {
                    break;
                }
                bool ret = ParseHttpHead(line);
                if(ret == false)
                {
                    return false;
                }
            }
            //头部处理完毕，进入正文获取阶段
            _recvStatus = RECV_HTTP_BODY;
            return true;
        }
        //解析请求头部
        bool ParseHttpHead(std::string& line)
        {
            //末尾是换行字符则去掉 \r\n
            if(line.back() == '\n')
            {
                line.pop_back();
            }
            if(line.back() == '\r')
            {
                line.pop_back();
            }
            //key: val\r\n
            size_t pos = line.find(":");
            if(pos == std::string::npos)
            {
                _recvStatus = RECV_HTTP_HEAD; 
                _respStatus = 400;  //BDA REQUEST
                return false;
            }
            std::string key = line.substr(0, pos);
            std::string val = line.substr(pos+2);
            _request.SetHeader(key, val);
            return true;
        }
        //接收请求正文
        bool RecvHttpBody(Muduo::Buffer* buf)
        {
            if(_recvStatus != RECV_HTTP_BODY)
            {
                return false;
            }
            //1. 获取正文长度
            size_t contentLen = _request.ContentLength();
            if(contentLen == 0)
            {
                //没有正文，则请求接收解析完毕
                _recvStatus = RECV_HTTP_OVER;
                return true;
            }
            //2. 当前已经接收了多少正文---即_request._body的长度
            size_t realLen = contentLen - _request._body.size();//还需要接收的正文长度
            //3. 接收正文放到body中，但是也要考虑当前缓冲区中的数据，是否是全部正文
            //3.1 缓冲区中的数据，包含了当前请求的正文，则取出所需的数据
            if(buf->ReadAbleSize() >= realLen)
            {
                _request._body += buf->ReadAsStringAndPop(realLen);
                _recvStatus = RECV_HTTP_OVER;
                return true;
            }
            //3.2 缓冲区中的数据，无法满足当前正文的需要，数据不足，先取出这些数据放到 _request._body中，然后等待新的数据到来
            _request._body += buf->ReadAsStringAndPop(buf->ReadAbleSize());
            return true;
        }
    public:
        HttpContext()
        :_respStatus(200), _recvStatus(RECV_HTTP_LINE)
        {}
        //重置上下文
        void ReSet()
        {
            _respStatus = 200;
            _recvStatus = RECV_HTTP_LINE;
            _request.ReSet();
        }
        //获取响应状态码
        int ResponseStatus()
        {
            return _respStatus;
        }
        //获取接收状态
        HttpRecvStatus RecvStatus()
        {
            return _recvStatus;
        }
        //获取请求信息
        HttpRequest& Request()
        {
            return _request;;
        }
        //接收并解析HTTP请求  
        void RecvHttpRequest(Muduo::Buffer* buf)
        {
            //不同的状态做不同的事情，但是这里不要break，因为处理完请求行后，应该立即处理头部，而不是退出等新数据
            switch(_recvStatus)
            {
                case RECV_HTTP_LINE:
                    RecvHttpLine(buf);
                case RECV_HTTP_HEAD:
                    RecvHttpHead(buf);
                case RECV_HTTP_BODY:
                    RecvHttpBody(buf);
            }
        }
    };

    const size_t DefaultTimeout = 10;
    class HttpServer
    {
        using Handler = std::function<void(const Muduo::HttpRequest&, Muduo::HttpResponse*)>;
        using Handlers = std::vector<std::pair<std::regex, Handler>>;
    private:
        Handlers _getRoute; //get方法路由表
        Handlers _postRoute;    //post方法路由表
        Handlers _putRoute;     //put方法路由表
        Handlers _deleteRoute;  //delete方法路由表
        std::string _baseDir;   //静态资源根目录
        Muduo::TcpServer _server;
    private:
        //将HttpResponse中的要素按照Http协议格式进行组织，发送
        void WriteResponse(const Muduo::PtrConnection& conn, Muduo::HttpRequest& req, Muduo::HttpResponse& rsp)
        {
            //1. 先完善头部字段
            if(req.Close() == true)
            {
                rsp.SetHeader("Connection", "close");
            }
            else
            {
                rsp.SetHeader("Connection", "keep-alive");
            }
            if(rsp._body.empty() == false && rsp.HasHeader("Content-Length") == false)
            {   
                rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
            }
            if(rsp._body.empty() == false && rsp.HasHeader("Content-Type") == false)
            {   
                rsp.SetHeader("Content-Type", "application/octet-stream");
            }
            if(rsp._redirectFlag == true)
            {
                rsp.SetHeader("Location", rsp._redirectUrl);
            }
            //2. 将rsp中的要素，按照http协议格式进行组织
            std::string rspStr;
            //添加响应行
            rspStr += rsp._version + " " + std::to_string(rsp._statCode) + " " + Util::StatuDesc(rsp._statCode) + "\r\n";
            //添加响应头部字段
            for(auto& [first, second] : rsp._headers)
            {
                rspStr += first + ": " + second + "\r\n";
            }
            //添加空行
            rspStr += "\r\n";
            //添加正文
            rspStr += rsp._body;
            //3. 发送数据
            conn->Send(rspStr.data(), rspStr.size());
        }
        //静态资源的请求处理---将静态资源文件的数据读取出来，放到rsp._body中，并且设置Content-Type的类型
        void FileHandler(const Muduo::HttpRequest& req, Muduo::HttpResponse* rsp)
        {
            /*请求的资源必须存在, 且是一个普通文件---有一种特殊的请求，请求的是目录 ，这种情况给后边默认追加一个index.html
            不要忘了前缀的相对根目录,也就是将请求路径转换为实际存在的路径  /image/a.png  ->   ./wwwroot/image/a.png*/
            std::string path = _baseDir + req._path;//为了避免直接修改请求的资源路径，所以定义一个临时对象
            if(req._path.back() == '/')
            {
                path += "index.html";
            }
            //1. 读取静态资源的文件数据
            bool ret = Util::ReadFile(path, &(rsp->_body));
            if(ret == false)
            {
                return;
            }
            std::string mime = Util::ExMime(path);
            rsp->SetHeader("Content-Type", mime);
            return;
        }
        //判断一个请求是否是一个静态资源请求
        bool IsFileHandler(const Muduo::HttpRequest& req)
        {
            //是一个静态资源请求需要满足的要求：
            //1. 必须设置了静态资源根目录(web根目录)
            if(_baseDir.empty())
            {
                return false;
            }
            //2. 请求方法，必须是GET 或者 HEAD 请求方法
            if(req._method != "GET" && req._method != "HEAD")
            {
                return false;
            }
            //3. 请求的资源路径必须是合法路径
            if(Util::ValidPath(req._path) == false)
            {
                return false;
            }
            //4. 请求的资源必须存在, 且是一个普通文件---有一种特殊的请求，请求的是目录 ，这种情况给后边默认追加一个index.html
            //不要忘了前缀的相对根目录,也就是将请求路径转换为实际存在的路径  /image/a.png  ->   ./wwwroot/image/a.png
            std::string path = _baseDir + req._path;//为了避免直接修改请求的资源路径，所以定义一个临时对象
            if(req._path.back() == '/')
            {
                path += "index.html";
            }
            if(Util::IsRegular(path) == false)
            {
                return false;
            }
            return true;
        }
        //功能性的请求的分类处理
        void Dispatcher(Muduo::HttpRequest& req, Muduo::HttpResponse* rsp, Handlers& handlers)
        {
            //在对应请求方法的路由表中，查找是否含有对应资源请求的处理函数，有则调用，没有则设置响应状态码为404
            //思想：路由表存储的是正则表达式和对应处理函数的键值对
            //使用正则表达式，对请求的资源路径进行正则匹配，匹配成功就使用对应函数进行处理
            // /numbers/(\d+)     /numbers/12345
            for(auto& [a, b] : handlers)
            { 
                bool ret = std::regex_match(req._path, req._matches, a);
                if(ret == false)
                {
                    continue;
                }
                return b(req, rsp);//传入请求信息，和空的rsp，执行处理函数  
            }
            rsp->_statCode = 404;
        }
        //路由的查找和匹配
        void Route(Muduo::HttpRequest& req, Muduo::HttpResponse* rsp)
        {
            //1. 对请求进行分辨，是一个静态资源请求，还是一个功能性请求
            //静态资源请求，则进行静态资源的处理
            //功能性请求，则需要通过几个请求路由表来确定是否有处理函数
            //如果既不是静态资源请求，也没有设置对应的功能性请求处理函数，就返回404
            //GET， HEAD方法都先默认为是静态资源请求，但是要注意这两个方法有可能是功能性请求，如果FileHandler返回true，说明静态资源请求成功
            //返回false，说明可能是一个功能性请求
            if(IsFileHandler(req) == true)//先判断是否为静态资源请求
            {
                //是一个静态资源请求
                return FileHandler(req, rsp);
            }
            //走到这里就在路由表中查找功能性请求对应的处理函数
            if(req._method == "GET" || req._method == "HEAD")
            {
                return Dispatcher(req, rsp, _getRoute);
            }
            else if(req._method == "POST")
            {
                return Dispatcher(req, rsp, _postRoute);
            }
            else if(req._method == "PUT")
            {
                return Dispatcher(req, rsp, _putRoute);
            }
            else if(req._method == "DELETE")
            {
                return Dispatcher(req, rsp, _deleteRoute);
            }
            //走到这里说明既不是静态资源请求，也不是功能性资源请求
            rsp->_statCode = 405;   // Method Not Allowed
            return;
        }
        //设置上下文
        void OnConnected(const Muduo::PtrConnection& conn)
        {
            conn->SetContext(Muduo::HttpContext());
            LOGDEBUG("new connection %p", conn.get());
        }
        //缓冲区中的数据解析出错，错误响应的组织
        void ErrorHandler(const Muduo::HttpRequest& req, Muduo::HttpResponse* rsp)
        {
            //1. 组织一个错误展示页面
            std::string body;
            body += "<html>";
            body += "<head>";
            body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";
            body += "</head>";
            body += "<body>";
            body += "<h1>";
            body += std::to_string(rsp->_statCode);
            body += " ";
            body += Util::StatuDesc(rsp->_statCode);
            body += "</h1>";
            body += "</body>";
            body += "</html>";
            //2. 将页面数据，当做响应正文，放入rsp中
            rsp->SetContent(body);
        }
        //缓冲区数据解析+处理
        void OnMessage(const Muduo::PtrConnection& conn, Muduo::Buffer* buf)
        {
            // 只要缓冲区中还有数据就进行下一条请求的处理；
            while(buf->ReadAbleSize() > 0)
            {
                //1. 获取上下文
                Muduo::HttpContext* context = conn->GetContext()->Get<HttpContext>();
                //2. 通过上下文对缓冲区数据进行解析，得到HttpRequest对象
                //(1)如果缓冲区的数据解析出错，就直接回复出错响应
                //(2)如果解析正常，且请求已经获取完毕，才开始去进行处理
                context->RecvHttpRequest(buf);
                Muduo::HttpRequest& req = context->Request();            
                Muduo::HttpResponse rsp;
                if(context->ResponseStatus() >= 400)
                {
                    //进行错误响应，关闭连接
                    ErrorHandler(req, &rsp); //填充一个错误显示页面数据到rsp中
                    WriteResponse(conn, req, rsp);  //组织需要发送给客户端
                    //响应出错了就应该重置上下文
                    context->ReSet();
                    //出错后直接将该连接的接收缓冲区清空；
                    buf->MoveReadOffest(buf->ReadAbleSize());
                    conn->Shutdown();   //关闭连接
                    return;
                }
                if(context->RecvStatus() != RECV_HTTP_OVER)
                {
                    //说明当前请求还没有接收完整，则退出，等新数据到来在重新继续处理
                    return;
                }
                //走到这里说明一个请求接收完整了，开始请求的处理
                //3. 请求路由 + 业务处理
                Route(req, &rsp);
                //4. 对HttpResponse进行组织发送
                LOGDEBUG("%s", rsp._body.c_str());
                WriteResponse(conn, req, rsp);
                //5.重置上下文，只有重置了上下文，下一个请求的处理才不会受到本次请求的影响
                context->ReSet();
                //6. 根据长短连接判断是否关闭连接或者继续处理 
                if(rsp.Close() == true)
                {
                    //短连接则直接关闭连接
                    conn->Shutdown();
                }
            } 
        }
    public:
        HttpServer(int port, int timeout = DefaultTimeout)
        :_server(port)
        {
            //Http服务器的连接的超时销毁是默认开启的，是为了防止遇到恶意连接
            _server.EnableInactiveRelease(timeout);
            //给TcpServer设置对应的回调函数
            _server.SetConnectedCallback(std::bind(&HttpServer::OnConnected, this, std::placeholders::_1));
            _server.SetMessageCallback(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
        }
        //设置静态资源路径(web根目录)
        void SetBaseDir(const std::string& path)
        {
            if(Util::IsDirectory(path) == false)
            {
                //如果给的web根目录不存在就创建---直接退出
                LOGERROR("web root directory not exist!");
                exit(-1);
            }
            _baseDir = path;
        }
        //往get路由表中添加get方法的请求信息和与之对应的处理函数
        void Get(const std::string& pattern, const Handler& handler)
        {
            _getRoute.push_back({std::regex(pattern), handler});
        }
        //往post路由表中添加post方法的请求信息和与之对应的处理函数
        void Post(const std::string& pattern, const Handler& handler)
        {
            _postRoute.push_back({std::regex(pattern), handler});

        }
        //往put路由表中添加put方法的请求信息和与之对应的处理函数
        void Put(const std::string& pattern, const Handler& handler)
        {
            _putRoute.push_back({std::regex(pattern), handler});
        }
        //往delete路由表中添加delete方法的请求信息和与之对应的处理函数
        void Delete(const std::string& pattern, const Handler& handler)
        {
            _deleteRoute.push_back({std::regex(pattern), handler});
        }
        //设置从属线程的数量
        void SetThreadCount(int count)
        {
            _server.SetThreadCount(count);
        }
        //启动非活跃连接的超时销毁
        void EnableInactiveRelease(int timeout)
        {
            _server.EnableInactiveRelease(timeout);
        }
        //启动HttpServer
        void Start()
        {
            _server.Start();
        }

    };
}
#endif